/*
 * @Author: hongbin
 * @Date: 2023-03-27 20:18:15
 * @LastEditors: hongbin
 * @LastEditTime: 2023-03-28 15:07:09
 * @Description:
 */
import * as THREE from "three";
import vertexShader from "./shader/vt.glsl";
import fragmentShader from "./shader/fm.glsl";

export class ParticleEffect {
    bufferGeometry = new THREE.BufferGeometry();
    particle = new THREE.Points() as THREE.Points<
        THREE.BufferGeometry,
        THREE.ShaderMaterial
    >;
    position: Float32Array;
    pointNumber: number;
    toPosition: Float32Array;
    speed: Float32Array;
    color: Float32Array;
    toColor: Float32Array;
    scale: Float32Array;
    initPosition: Float32Array;
    initColor: THREE.Color;

    constructor(
        pointNumber: number,
        initColor = new THREE.Color("#000"),
        initRange = 400
    ) {
        this.pointNumber = pointNumber;
        this.initPosition = new Float32Array(pointNumber * 3);
        this.initColor = initColor;
        this.position = new Float32Array(pointNumber * 3);
        this.toPosition = new Float32Array(pointNumber * 3);
        this.color = new Float32Array(pointNumber * 3);
        this.toColor = new Float32Array(pointNumber * 3);
        this.speed = new Float32Array(pointNumber);
        this.scale = new Float32Array(pointNumber);

        this.bufferGeometry.setAttribute(
            "position",
            new THREE.BufferAttribute(this.position, 3)
        );
        this.bufferGeometry.setAttribute(
            "toPosition",
            new THREE.BufferAttribute(this.toPosition, 3)
        );
        this.bufferGeometry.setAttribute(
            "color",
            new THREE.BufferAttribute(this.color, 3)
        );
        this.bufferGeometry.setAttribute(
            "toColor",
            new THREE.BufferAttribute(this.toColor, 3)
        );
        this.bufferGeometry.setAttribute(
            "speed",
            new THREE.BufferAttribute(this.speed, 1)
        );
        this.bufferGeometry.setAttribute(
            "scale",
            new THREE.BufferAttribute(this.scale, 1)
        );

        this.particle.geometry = this.bufferGeometry;

        this.particle.material = new THREE.ShaderMaterial({
            vertexShader,
            fragmentShader,
            uniforms: {
                color: { value: new THREE.Color("#fff") },
                progress: { value: 0 },
            },
            blending: THREE.AdditiveBlending,
            transparent: true,
            depthTest: false,
        });
        this.init(initColor, initRange);
    }

    init(initColor: THREE.Color, initRange: number) {
        const { random } = Math;
        for (let index = 0; index < this.pointNumber; index++) {
            const i3 = index * 3;
            const x = (0.5 - random()) * initRange;
            const y = (0.5 - random()) * initRange;
            const z = (0.5 - random()) * initRange;
            this.toPosition[i3] = x;
            this.toPosition[i3 + 1] = y;
            this.toPosition[i3 + 2] = z;

            this.initPosition[i3] = x;
            this.initPosition[i3 + 1] = y;
            this.initPosition[i3 + 2] = z;

            this.scale[index] = random() * 2.5;

            this.toColor[i3] = initColor["r"];
            this.toColor[i3 + 1] = initColor["g"];
            this.toColor[i3 + 2] = initColor["b"];
        }
        this.bufferGeometry.attributes.position.needsUpdate = true;
        this.bufferGeometry.attributes.toPosition.needsUpdate = true;
        this.bufferGeometry.attributes.scale.needsUpdate = true;
        this.bufferGeometry.attributes.toColor.needsUpdate = true;
    }

    setProgress(progress: number) {
        this.particle.material.uniforms["progress"].value = progress;
    }

    translate(
        from: { geometry: THREE.BufferGeometry; color: THREE.Color },
        to: { geometry: THREE.BufferGeometry; color: THREE.Color }
    ) {
        const toArray = to.geometry.getAttribute("position").array;
        const fromArray = from.geometry.getAttribute("position").array;

        const toLength = toArray.length / 3;
        const fromLength = fromArray.length / 3;

        let toIndex = 0;
        let fromIndex = 0;

        for (let i = 0; i < this.pointNumber; i++) {
            const i3 = i * 3;
            toIndex = toIndex % toLength;
            fromIndex = fromIndex % fromLength;
            const toIndex3 = toIndex * 3;
            const fromIndex3 = fromIndex * 3;

            this.position[i3] = fromArray[fromIndex3];
            this.position[i3 + 1] = fromArray[fromIndex3 + 1];
            this.position[i3 + 2] = fromArray[fromIndex3 + 2];

            this.toPosition[i3] = toArray[toIndex3];
            this.toPosition[i3 + 1] = toArray[toIndex3 + 1];
            this.toPosition[i3 + 2] = toArray[toIndex3 + 2];

            this.color[i3] = from.color["r"];
            this.color[i3 + 1] = from.color["g"];
            this.color[i3 + 2] = from.color["b"];

            this.toColor[i3] = to.color["r"];
            this.toColor[i3 + 1] = to.color["g"];
            this.toColor[i3 + 2] = to.color["b"];

            this.speed[i] = 0.3 + Math.random() * 0.7;

            toIndex++;
            fromIndex++;
        }

        this.bufferGeometry.attributes.position.needsUpdate = true;
        this.bufferGeometry.attributes.toPosition.needsUpdate = true;
        this.bufferGeometry.attributes.color.needsUpdate = true;
        this.bufferGeometry.attributes.toColor.needsUpdate = true;
        this.bufferGeometry.attributes.speed.needsUpdate = true;
    }

    to(geometry: THREE.BufferGeometry, color: THREE.Color) {
        const target = geometry.getAttribute("position").array;

        const targetLength = target.length / 3;
        let targetIndex = 0;

        for (let i = 0; i < this.pointNumber; i++) {
            const i3 = i * 3;
            targetIndex = targetIndex % targetLength;
            const targetIndex3 = targetIndex * 3;

            this.position[i3] = this.toPosition[i3];
            this.position[i3 + 1] = this.toPosition[i3 + 1];
            this.position[i3 + 2] = this.toPosition[i3 + 2];

            this.toPosition[i3] = target[targetIndex3];
            this.toPosition[i3 + 1] = target[targetIndex3 + 1];
            this.toPosition[i3 + 2] = target[targetIndex3 + 2];

            this.color[i3] = this.toColor[i3];
            this.color[i3 + 1] = this.toColor[i3 + 1];
            this.color[i3 + 2] = this.toColor[i3 + 2];

            this.toColor[i3] = color.r;
            this.toColor[i3 + 1] = color.g;
            this.toColor[i3 + 2] = color.b;

            this.speed[i] = 0.3 + Math.random() * 0.7;

            targetIndex++;
        }

        this.bufferGeometry.attributes.position.needsUpdate = true;
        this.bufferGeometry.attributes.speed.needsUpdate = true;
        this.bufferGeometry.attributes.toPosition.needsUpdate = true;
        this.bufferGeometry.attributes.color.needsUpdate = true;
        this.bufferGeometry.attributes.toColor.needsUpdate = true;
    }
}
